package middleware

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	uuid "github.com/satori/go.uuid"
	"net/url"
	"runtime"
	"strings"
	pkg "sunliang.cc/package"
	"sunliang.cc/package/resp"
)

func Recover() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		defer func() {
			var errRecover interface{}
			if errRecover = recover(); errRecover == nil {
				return
			}

			// 对请求地址的参数解码，方便查看并复现错误
			reqPath := reqURI(ctx.Request.RequestURI)
			// 设置TraceId方便错误调试
			ctxTrace, ok := ctx.Get("ctx")
			var traceId string
			if ok {
				traceId = ctxTrace.(context.Context).Value("trace_id").(string)
			} else {
				traceId = uuid.NewV4().String()
			}
			traceResult := panicTrace(errRecover)
			pkg.Logger.WithFields(map[string]interface{}{
				"path":     reqPath,
				"ip":       ctx.ClientIP(),
				"method":   ctx.Request.Method,
				"form":     form(ctx.Request.Form),
				"ua":       ctx.Request.UserAgent(),
				"debug":    traceResult,
				"trace_id": traceId,
			}).Error()

			errMsg := strings.Split(traceResult, "\n")[0]
			if !strings.Contains(errMsg, resp.PanicTarget()) {
				if pkg.FlagConfig.Env == "prod" {
					errMsg = "It's a 500"
				}
			} else {
				errMsg = resp.GetPanic(errMsg)
			}
			resp.Result{Ctx: ctx}.M(errMsg).Fail()
			ctx.Abort()
		}()
		ctx.Next()
	}
}

func panicTrace(err interface{}) string {
	buf := new(bytes.Buffer)
	_, _ = fmt.Fprintf(buf, "%v\n", err)
	for i := 1; ; i++ {
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		_, _ = fmt.Fprintf(
			buf, "%s:%d (0x%x)\n", file, line, pc,
		)
	}
	return buf.String()
}

func reqURI(requestURI string) string {
	path, err := url.QueryUnescape(requestURI)
	if err != nil {
		path = requestURI
	}
	return path
}

func form(requestForm url.Values) string {
	if len(requestForm) == 0 {
		return `{}`
	}
	var forms map[string][]string = requestForm
	bodyMap := make(map[string]interface{}, len(forms)+1)
	for k, v := range forms {
		if len(v) == 1 {
			bodyMap[k] = v[0]
		}
	}
	if mapString, err := json.Marshal(bodyMap); err != nil {
		return err.Error()
	} else {
		return string(mapString)
	}
}
